home *** CD-ROM | disk | FTP | other *** search
- #
- # The Python Imaging Library.
- # $Id: GifImagePlugin.py,v 1.1.1.1 1998/08/18 13:07:51 sjoerd Exp $
- #
- # GIF file handling
- #
- # History:
- # 95-09-01 fl Created
- # 96-12-14 fl Added interlace support
- # 96-12-30 fl Added animation support
- # 97-01-05 fl Added write support, fixed local colour map bug
- # 97-02-23 fl Make sure to load raster data in getdata()
- # 97-07-05 fl Support external decoder
- # 98-07-09 fl Handle all modes when saving
- # 98-07-15 fl Renamed offset attribute to avoid name clash
- #
- # Copyright (c) Secret Labs AB 1997-98.
- # Copyright (c) Fredrik Lundh 1995-97.
- #
- # See the README file for information on usage and redistribution.
- #
-
-
- __version__ = "0.5"
-
-
- import array
- import Image, ImageFile, ImagePalette
-
-
- # --------------------------------------------------------------------
- # Helpers
-
- def i16(c):
- return ord(c[0]) + (ord(c[1])<<8)
-
- def o16(i):
- return chr(i&255) + chr(i>>8&255)
-
-
- # --------------------------------------------------------------------
- # Identify/read GIF files
-
- def _accept(prefix):
- return prefix[:6] in ["GIF87a", "GIF89a"]
-
- class GifImageFile(ImageFile.ImageFile):
-
- format = "GIF"
- format_description = "Compuserve GIF"
-
- def data(self):
- s = self.fp.read(1)
- if s and ord(s):
- return self.fp.read(ord(s))
- return None
-
- def _open(self):
-
- # Screen
- s = self.fp.read(13)
- if s[:6] not in ["GIF87a", "GIF89a"]:
- raise SyntaxError, "not a GIF file"
-
- self.info["version"] = s[:6]
-
- self.size = i16(s[6:]), i16(s[8:])
-
- self.tile = []
-
- flags = ord(s[10])
-
- bits = (flags & 7) + 1
-
- if flags & 128:
- # get global palette
- self.info["background"] = ord(s[11])
- self.global_palette = self.palette =\
- ImagePalette.raw("RGB", self.fp.read(3<<bits))
-
- self.__fp = self.fp # FIXME: hack
- self.__offset = 0
- self.dispose = None
-
- self.frame = -1
- self.seek(0) # get ready to read first frame
-
- def seek(self, frame):
-
- # FIXME: can only seek to next frame; should add rewind capability
- if frame != self.frame + 1:
- raise ValueError, "cannot seek to frame %d" % frame
- self.frame = frame
-
- self.tile = []
-
- self.fp = self.__fp
- if self.__offset:
- # backup to last frame
- self.fp.seek(self.__offset)
- while self.data():
- pass
- self.__offset = 0
-
- if self.dispose:
- self.im = self.dispose
- self.dispose = None
-
- self.palette = self.global_palette
-
- while 1:
-
- s = self.fp.read(1)
- if not s or s == ";":
- break
-
- elif s == "!":
- #
- # extensions
- #
- s = self.fp.read(1)
- block = self.data()
- if ord(s) == 249:
- #
- # graphic control extension
- #
- flags = ord(block[0])
- if flags & 1:
- self.info["transparency"] = ord(block[3])
- self.info["duration"] = i16(block[1:3]) * 10
- try:
- # disposal methods
- if flags & 8:
- # replace with background colour
- self.dispose = Image.core.fill("P", self.size,
- self.info["background"])
- elif flags & 16:
- # replace with previous contents
- self.dispose = self.im.copy()
- except (AttributeError, KeyError):
- pass
- elif ord(s) == 255:
- #
- # application extension
- #
- self.info["extension"] = block, self.fp.tell()
- if block[:11] == "NETSCAPE2.0":
- self.info["loop"] = 1 # FIXME
- while self.data():
- pass
-
- elif s == ",":
- #
- # local image
- #
- s = self.fp.read(9)
-
- # extent
- x0, y0 = i16(s[0:]), i16(s[2:])
- x1, y1 = x0 + i16(s[4:]), y0 + i16(s[6:])
- flags = ord(s[8])
-
- interlace = (flags & 64) != 0
-
- if flags & 128:
- bits = (flags & 7) + 1
- self.palette =\
- ImagePalette.raw("RGB", self.fp.read(3<<bits))
-
- # image data
- bits = ord(self.fp.read(1))
- self.__offset = self.fp.tell()
- self.tile = [("gif",
- (x0, y0, x1, y1),
- self.__offset,
- (bits, interlace))]
- break
-
- else:
- pass
- # raise IOError, "illegal GIF tag `%x`" % ord(s)
-
- if not self.tile:
- self.__fp = None
- raise EOFError, "no more images in GIF file"
-
- self.mode = "L"
- if self.palette:
- self.mode = "P"
-
- def tell(self):
- return self.frame
-
-
- # --------------------------------------------------------------------
- # Write GIF files
-
- try:
- import _imaging_gif
- except ImportError:
- _imaging_gif = None
-
- RAWMODE = {
- "1": "L",
- "L": "L",
- "P": "P",
- }
-
- def _save(im, fp, filename):
-
- if _imaging_gif:
- # call external driver
- try:
- _imaging_gif.save(im, fp, filename)
- return
- except IOError:
- pass # write uncompressed file
-
- try:
- rawmode = RAWMODE[im.mode]
- imOut = im
- except KeyError:
- # convert on the fly (EXPERIMENTAL -- I'm not sure PIL
- # should automatically convert images on save...)
- if Image.getmodebase(im.mode) == "RGB":
- imOut = im.convert("P")
- rawmode = "P"
- else:
- imOut = im.convert("L")
- rawmode = "L"
-
- # header
- for s in getheader(imOut):
- fp.write(s)
-
- flags = 0
-
- try:
- interlace = im.encoderinfo["interlace"]
- except:
- # default is on (since we're writing uncompressed images)
- interlace = 1
- flags = flags | 64
-
- # local image header
- fp.write("," +
- o16(0) + o16(0) + # bounding box
- o16(im.size[0]) + # size
- o16(im.size[1]) +
- chr(flags) + # flags
- chr(8)) # bits
-
- imOut.encoderconfig = (8, interlace)
-
- ImageFile._save(imOut, fp, [("gif", (0,0)+im.size, 0, rawmode)])
-
- fp.write("\0") # end of image data
-
- fp.write(";") # end of file
-
- try:
- fp.flush()
- except: pass
-
- def _save_netpbm(im, fp, filename):
-
- #
- # If you need real GIF compression and/or RGB quantization, you
- # can use the external NETPBM/PBMPLUS utilities. See comments
- # below for information on how to enable this.
-
- import os
- file = im._dump()
- if im.mode != "RGB":
- os.system("ppmtogif %s >%s" % (file, filename))
- else:
- os.system("ppmquant 256 %s | ppmtogif >%s" % (file, filename))
- try: os.unlink(file)
- except: pass
-
-
- # --------------------------------------------------------------------
- # GIF utilities
-
- def getheader(im):
- """Return a list of strings representing a GIF header"""
-
- s = [
- "GIF87a" + # magic
- o16(im.size[0]) + # size
- o16(im.size[1]) +
- chr(7 + 128) + # flags: bits + palette
- chr(0) + # background
- chr(0) # reserved/aspect
- ]
-
- # global palette
- if im.mode == "P":
- # colour palette
- s.append(im.im.getpalette("RGB"))
- else:
- # greyscale
- for i in range(256):
- s.append(chr(i) * 3)
-
- return s
-
- def getdata(im, offset = (0, 0), **params):
- """Return a list of strings representing this image.
- The first string is a local image header, the rest contains
- encoded image data."""
-
- class collector:
- data = []
- def write(self, data):
- self.data.append(data)
-
- im.load() # make sure raster data is available
-
- fp = collector()
-
- try:
- im.encoderinfo = params
-
- # local image header
- fp.write("," +
- o16(offset[0]) + # offset
- o16(offset[1]) +
- o16(im.size[0]) + # size
- o16(im.size[1]) +
- chr(0) + # flags
- chr(8)) # bits
-
- ImageFile._save(im, fp, [("gif", (0,0)+im.size, 0, RAWMODE[im.mode])])
-
- fp.write("\0") # end of image data
-
- finally:
- del im.encoderinfo
-
- return fp.data
-
-
- # --------------------------------------------------------------------
- # Registry
-
- Image.register_open(GifImageFile.format, GifImageFile, _accept)
- Image.register_save(GifImageFile.format, _save)
- Image.register_extension(GifImageFile.format, ".gif")
- Image.register_mime(GifImageFile.format, "image/gif")
-
- #
- # Uncomment the following line if you wish to use NETPBM/PBMPLUS
- # instead of the built-in "uncompressed" GIF encoder
-
- # Image.register_save(GifImageFile.format, _save_netpbm)
-